/*
 Copyright 1985-2014 SHAWN BAKHTIAR. All rights reserved.
 
 Redistribution and use in source and binary forms, 
 with or without modification, are permitted 
 provided that the following conditions are met:
 
 Redistributions of source code must retain the above 
 copyright notice, this list of conditions and the following 
 disclaimer.
 
 Redistributions in binary form must reproduce the above 
 copyright notice, this list of conditions and the following 
 disclaimer in the documentation and/or other materials 
 provided with the distribution.
 
 THIS SOFTWARE IS PROVIDED BY THE SHAWN BAKHTIAR ``AS IS''
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 SHALL THE SHAWN BAKHTIAR OR CONTRIBUTORS BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 OF SUCH DAMAGE.
 
 The views and conclusions contained in the software and documentation 
 are those of the authors and should not be interpreted as representing 
 official policies, either expressed or implied, of SHAWN BAKHTIAR.
 */


#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>


#include <regex.h>

#define MAX_STRING_SIZE 255
#define MAX_TXRX_BUFFER 1024
#define MAX_HEADER_BUFFER 8192

typedef struct {
    
    int sockfd;
    short port;
    char host[MAX_STRING_SIZE];
    char user[MAX_STRING_SIZE];
    char pass[MAX_STRING_SIZE];
    
    
} IMAPConnection;



void IMAP_server_process_message(IMAPConnection *icon_p, long id){

    /* Local Variables */
    long n = 0;
    char sendBuff[MAX_TXRX_BUFFER];
    char recvBuff[MAX_TXRX_BUFFER];
    char commandBuff[MAX_TXRX_BUFFER];
    char headerBuff[MAX_HEADER_BUFFER];
    char *cur_toke = NULL;
    char *prev_toke = NULL;
    char *tokes_saveptr = NULL;
    char *found;
    
    regex_t preg;
    regmatch_t pmatch[10];
    short deca[4] = {0,0,0,0};
    
    /* Zero out buffers */
    bzero(sendBuff, MAX_TXRX_BUFFER);
    bzero(recvBuff, MAX_TXRX_BUFFER);
    bzero(commandBuff, MAX_TXRX_BUFFER);
    bzero(headerBuff, MAX_HEADER_BUFFER);
    
    /* Compile regular expression to use for finding/parsing IP address  */
    if ( regcomp(&preg, "([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)",REG_ICASE | REG_EXTENDED) != 0 ) return;
    
    /* Fetch header information for message */
    snprintf(sendBuff, MAX_TXRX_BUFFER, "A1 FETCH %ld BODY[HEADER]\r\n",id);
    write(icon_p->sockfd,sendBuff,strlen(sendBuff));
    
    /* Manditory sleep to allow IMAP server to respond */
    sleep(1);
    
    /* Read up to max header buffer size */
    n = read(icon_p->sockfd, headerBuff, MAX_HEADER_BUFFER-1);

    /* Loop through header data and find SMTP match string */
    for (cur_toke = strtok_r(headerBuff, "\t\r\n",&tokes_saveptr);
         cur_toke != NULL;
         cur_toke = strtok_r(NULL, "\t\r\n",&tokes_saveptr) ){
        
        /* If we find match and previous token (the one containing the sender is not NULL, process */
        if(((found = strstr(cur_toke, "by smtp.inksystems.com")) != NULL) && prev_toke != NULL) {
            
            /* Check against compiled expression */
            if(regexec(&preg, prev_toke, 10, pmatch, 0) == 0){
                
                /* Delimited dotet decimal notation for converting to numbers */
                prev_toke[pmatch[0].rm_eo] = '\0';
                prev_toke[pmatch[1].rm_eo] = '\0';
                prev_toke[pmatch[2].rm_eo] = '\0';
                prev_toke[pmatch[3].rm_eo] = '\0';
                prev_toke[pmatch[4].rm_eo] = '\0';
                
                /* Get actual short values for each octet */
                deca[0] = (short) strtod(prev_toke + pmatch[1].rm_so,NULL);
                deca[1] = (short) strtod(prev_toke + pmatch[2].rm_so,NULL);
                deca[2] = (short) strtod(prev_toke + pmatch[3].rm_so,NULL);
                deca[3] = (short) strtod(prev_toke + pmatch[4].rm_so,NULL);
            
                /* Create nsupdate command string */
                snprintf(commandBuff,
                         MAX_TXRX_BUFFER,
                         "nsupdate <<'EOF' \n " \
                         "server localhost \n" \
                         "zone dnsbl.inksystems.com \n"\
                         "update add %d.%d.%d.%d.dnsbl.inksystems.com 300 A 127.0.0.3 \n" \
                         "update add %d.%d.%d.%d.dnsbl.inksystems.com 300 A TXT \"spam source cought by autoDNSBL \" \n" \
                         "send\n quit\n EOF\n",
                         deca[3],deca[2],deca[1],deca[0],
                        deca[3],deca[2],deca[1],deca[0]);
                
                
                /* syscall nsupdate */
                // TODO: Given the Shellshock bug using system call may not be the best approach.
                //       also see: http://en.wikipedia.org/wiki/Return-to-libc_attack
                //      can we come up wiht a way to use the nsupdate.c or a library version to do this
                //      or cound we open a seconday conntection to the DNS server on port and send update strings directly?
                system(commandBuff);
                
                
            }
            
            
            /* Move processed messages to process folder */
            snprintf(sendBuff,MAX_TXRX_BUFFER,"A1 COPY %ld INBOX.PROCESSED\r\n",id);
            write(icon_p->sockfd,sendBuff,strlen(sendBuff));
            
            /* Read results (IGNORE) */
            n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER-1);
            
            /* Set moved message flag to deleted for INBOX */
            snprintf(sendBuff,MAX_TXRX_BUFFER,"A1 STORE %ld +FLAGS (\\Deleted)\r\n",id);
            write(icon_p->sockfd,sendBuff,strlen(sendBuff));
            
            /* Read results (IGNORE) */
            n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER-1);
        
            
        }
        
        prev_toke = cur_toke;
        
    }
    
    
    /* Release the regex allocation */
    regfree(&preg);
    
    
    
}





void IMAP_server_process_list(IMAPConnection *icon_p){
    
    /* Local Variables */
    long n = 0;
    long messid = 0;
    char recvBuff[MAX_TXRX_BUFFER];
    char sendBuff[MAX_TXRX_BUFFER];
    char *cur_toke;
    char *tokes_saveptr;
    
    /* Checks */
    if(icon_p == NULL) return;
    

    /* Clear memory receving buffer and address struct*/
    bzero(recvBuff,MAX_TXRX_BUFFER);
    bzero(sendBuff,MAX_TXRX_BUFFER);
    
    /* Select the inbox */
    snprintf(sendBuff, MAX_TXRX_BUFFER, "A1 SELECT INBOX\r\n");
    write(icon_p->sockfd,sendBuff,strlen(sendBuff));
    
    /* Get return from command */
    n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER-1);
    
    /* Fetch all inbox items */
    snprintf(sendBuff, MAX_TXRX_BUFFER, "A1 FETCH 1:* (FLAGS)\r\n");
    write(icon_p->sockfd,sendBuff,strlen(sendBuff));
    
    /* Read the result set */
    n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER-1);
    
    /* Loop through the list of messages in INBOX */
    for (cur_toke = strtok_r(recvBuff, "\r\n",&tokes_saveptr);
         cur_toke != NULL;
         cur_toke = strtok_r(NULL, "\r\n",&tokes_saveptr) ){
        
        /* Get the Message ID */
        messid = (long) strtod(cur_toke + 2, NULL);
        
        /* Process the message ID */
        IMAP_server_process_message(icon_p,messid);
        
    }
    
    /* Expunge current folder */
    snprintf(sendBuff,MAX_TXRX_BUFFER,"A1 EXPUNGE\r\n");
    write(icon_p->sockfd,sendBuff,strlen(sendBuff));
    
    /* Read command results (IGNORE) */
    n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER-1);
    
}







void IMAP_server_disco(IMAPConnection *icon_p){
    
    /* Checks */
    if(icon_p == NULL) return;
    

    close(icon_p->sockfd);
}





void IMAP_server_logout(IMAPConnection *icon_p){
    
    /* Local Variables */
    long n = 0;
    char recvBuff[MAX_TXRX_BUFFER];
    
    /* Close IMAP session */
    write(icon_p->sockfd, "A1 CLOSE\r\n", 10);
    
    /* Read results (IGNORE) */
    n = read(icon_p->sockfd, recvBuff, sizeof(recvBuff)-1);
    
    /* Logout of IMAP server */
    write(icon_p->sockfd, "A1 LOGOUT\r\n", 11);
    
    /* Read results (IGNORE) */
    n = read(icon_p->sockfd, recvBuff, sizeof(recvBuff)-1);
    

}



int IMAP_server_login(IMAPConnection *icon_p){
    
    /* Local Variables */
    long n = 0;
    char recvBuff[MAX_TXRX_BUFFER];
    char sendBuff[MAX_TXRX_BUFFER];
    
    /* Checks */
    if(icon_p == NULL) return 0;

    /* Zero out buffers */
    bzero(recvBuff,MAX_TXRX_BUFFER);
    bzero(sendBuff,MAX_TXRX_BUFFER);
    
    /* Create the login string */
    snprintf(sendBuff, MAX_TXRX_BUFFER, "A1 LOGIN \"%s\" \"%s\"\r\n",icon_p->user,icon_p->pass);
    
    /* Send login string to server */
    write(icon_p->sockfd, sendBuff, strlen(sendBuff));
    
    if( (n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER - 1)) <= 0) return 0;
    recvBuff[n] = 0;
    
    /* If we get * OK response return 1 for success, otherwize 0 */
    if(!strncmp(recvBuff, "A1 OK ", 5)){
        return 1;
    }
    
    
    return 0;
    
}




int IMAP_server_connect(IMAPConnection * icon_p){
    
    /* Local variables */
    long n = 0;
    struct sockaddr_in serv_addr;
    char recvBuff[MAX_TXRX_BUFFER];
    
    /* Checks */
    if(icon_p == NULL) return 0;
    
    /* Zero out buffers */
    bzero(recvBuff,MAX_TXRX_BUFFER);
    bzero(&serv_addr,sizeof(serv_addr));
    
    /* Creat the socket */
    if((icon_p->sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("IMAP_server_connect [ERROR]: Could not create socket \n");
        return 0;
    }
    
    /* Set protocal */
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(icon_p->port);
    
    /* Setup the connection */
    if(inet_pton(AF_INET, icon_p->host, &serv_addr.sin_addr)<=0)
    {
        printf("IMAP_server_connect [ERROR]: inet_pton error occured\n");
        return 0;
    }
    
    /* Connect to server */
    if( connect(icon_p->sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("\n IMAP_server_connect [ERROR]: Connect Failed \n");
        return 0;
    }
    
    /* Read line after we connect */
    if( (n = read(icon_p->sockfd, recvBuff, MAX_TXRX_BUFFER - 1)) <= 0) return 0;
    
    
    /* If we dont get * OK response close fd and return 0 */
    if(strncmp(recvBuff, "* OK ", 5)){
        close(icon_p->sockfd);
        return 0;
    }
    
    /* Otherwize return sockfd */
    return icon_p->sockfd;
    
    
}



int main(int argc, const char *argv[])
{

    /* Local variables */
    IMAPConnection icon;
    
    /* Zero out buffers */
    bzero(&icon,sizeof(IMAPConnection));
    
    
    /* Test for args -- NOTE THIS IS NOT WORKING YET WE ARE HARDCODING INFO FOR NOW
    if(argc != 2)
    {
        printf("\n Usage: %s <ip of server> \n",argv[0]);
        return 1;
    }
     */
    
     icon.port = 143;
     strncpy(icon.host,"127.0.0.1",9);
     strncpy(icon.user,"username",8);
     strncpy(icon.pass,"userpass",8);
     
 
    
    /* Go into infinit loop for daemon mode */
    // TODO: We need to make fit the linux model as defined:
    // http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html

    while(1){
        
        /* Open IMAP Connection */
        if(IMAP_server_connect(&icon)){
            
            
            /* Log into IMAP Server */
            if( IMAP_server_login(&icon)) {
                
                /* Process the new SPAM messages */
                IMAP_server_process_list(&icon);
                
                /* Logout of server on complete */
                IMAP_server_logout(&icon);
                
            }
            
            /* Close the connection */
            IMAP_server_disco(&icon);
 
        }
        
        /* Sleep a few seconds */
        sleep(3);
        
    }
    
    return 0;
}